-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement iter::Sum
and iter::Product
for Option
#58975
Conversation
This is similar to the existing implementation for `Result`. It will take each item into the accumulator unless a `None` is returned.
r? @kennytm (rust_highfive has picked a reviewer for you, use r? to override) |
r? @SimonSapin |
Previous PR about Sum for Option: #50884 |
@scottmcm thanks, I missed this PR in my search. If folks still think it shouldn't be in |
@jtdowney Yours is consistent in behaviour with Result, right? I think the other wasn't, so it's possible the outcome will be different here. |
Correct, this is PR is consistent with how |
ping from triage anyone from @rust-lang/libs can review this? |
This looks reasonable to me to merge! In that case I'll... @rfcbot fcp merge |
Team member @alexcrichton has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@rfcbot concern not sold on these impls The use cases that come to mind would all be more clearly written with a for-loop. Could you point out some places in real code where these impls would be beneficial? I can see that part of the motivation is to be consistent with Result's Sum and Product impls. But I find that the behavior of summing an iterator of Result<T, E> into a Result<T, E> is more intuitive to me than the behavior of summing Option<T> into an Option<T>. Looking at the example code from the PR: v.iter().map(|&x: &i32|
if x < 0 { None }
else { Some(x) }
).sum() it would be easy to read this as summing only those items that are Some and ignoring Nones. |
@dtolnay the specific case I hit when I discovered this was missing was taking a string, calling |
Co-Authored-By: jtdowney <[email protected]>
If it helps, here is the code I was trying to write: fn is_luhn_valid(value: &str) -> bool {
value
.chars()
.rev()
.map(|c| c.to_digit(10))
.enumerate()
.map(|(idx, n)| {
if idx % 2 != 0 {
n.map(|v| {
let v = v * 2;
if v > 9 {
v - 9
} else {
v
}
})
} else {
n
}
})
.sum::<Option<u32>>()
.map(|total| total % 10 == 0)
.unwrap_or(false)
}
fn main() {
dbg!(is_luhn_valid("4111111111111111"));
dbg!(is_luhn_valid("5454545454545454"));
} This is an implementation of the luhn check algorithm. |
I think a more inspired example in the documentation would be helpful -- something reasonably representative of the way that you might find this impl used in real code where it is the best solution to the problem being solved. (Not that doc comments inside trait impls end up all that visible. Current rustdoc would bury it pretty thoroughly. But it would serve as long-term documentation of the use cases that we wanted to support by providing the impl.)
I think unfortunately this applies to your Luhn implementation too. It is neat that you were able to find a way to express this all in a single method chain, and only one semicolon, but I think it comes out quite challenging to read. A reader looking to understand the implementation or code reviewer looking to confirm that it is correct would need to:
Here is how I might write this instead. My implementation is the same number of lines (24) but some of them are now blank. I find this one far easier to follow -- curious what you think. Even the signature of Based on this use case I would still side against accepting the new impls. fn is_luhn_valid(input: &str) -> bool {
match luhn_sum(input) {
Some(sum) => sum % 10 == 0,
None => false,
}
}
fn luhn_sum(input: &str) -> Option<u32> {
let mut sum = 0;
for (idx, ch) in input.chars().rev().enumerate() {
let digit = ch.to_digit(10)?;
if idx % 2 == 0 {
sum += digit;
} else if digit * 2 <= 9 {
sum += digit * 2;
} else {
sum += digit * 2 - 9;
}
}
Some(sum)
} Another possibility which is similarly readable: fn is_luhn_valid(input: &str) -> bool {
let mut sum = 0;
for (idx, ch) in input.chars().rev().enumerate() {
let digit = match ch.to_digit(10) {
Some(digit) => digit,
None => return false,
};
if idx % 2 == 0 {
sum += digit;
} else if digit * 2 <= 9 {
sum += digit * 2;
} else {
sum += digit * 2 - 9;
}
}
sum % 10 == 0
} |
I am happy to provide a better doc use case but we will have to agree to disagree about the for loop. You seem to be discounting the number of cases we can't think up in this PR and excusing a clear lack of symmetry in the API with regards to |
This seems like the usual matter of preference between an imperative vs. a functional style of writing the code. For example, I don't think doing
The functional style algorithm has
If this is a concern then extract the lambdas to the top level and you will have ~2 levels of indent. What is nice about @jtdowney's version is that it encourages separation of concerns and extraction into smaller steps. |
Thanks for the feedback -- I will give it some more thought under the understanding that @Centril finds the iterator chain easier to read. For now, registering the comment about example code in the documentation, which I would still like to see: @rfcbot concern more fleshed out example use case in documentation |
It might be appropriate to make the RFC of the API guideline about the equation of |
I feel this should only be implemented if there were a single obvious semantics derivable from the types involved, or at least one clearly more intuitive than others. Which doesn't appear to be the case. I, as well, would intuitively expect What I would rather see is separate iterator adapters implemented for (Edit: the latter of course already exists and is spelled |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon. |
r? @sfackler Good deeds begets more =P |
@Centril @dtolnay I don't undertand why the "not sold on these impl"-s concern was resolved---in particular, the ambiguity around filtering I accept that the filtering implementation may not be ideal either, but I don't get why we should commit to either if ambiguous. It's pretty easy to get the short-circuit behaviour with |
r? @dtolnay |
@bors r+ |
📌 Commit 422a4c0 has been approved by |
Bumped stable attrs to 1.37, since beta is currently 1.36. @bors r+ |
📌 Commit 9f8d934 has been approved by |
@cristicbz the discussion persuaded me that these impls are valuable and that some people find it the most natural and readable way to express certain real-world logic. I don't know that I would ever use them, but it seems to be a matter of preference in terms of what people's background leads them to find more readable. I don't think there is an ambiguity with filtering because filtering would not match the signature of these impl; it would produce |
…r=dtolnay Implement `iter::Sum` and `iter::Product` for `Option` This is similar to the existing implementation for `Result`. It will take each item into the accumulator unless a `None` is returned. I based a lot of this on rust-lang#38580. From that discussion it didn't seem like this addition would be too controversial or difficult. One thing I still don't understand is picking the values for the `stable` attribute. This is my first non-documentation PR for rust so I am open to any feedback on improvements.
…r=dtolnay Implement `iter::Sum` and `iter::Product` for `Option` This is similar to the existing implementation for `Result`. It will take each item into the accumulator unless a `None` is returned. I based a lot of this on rust-lang#38580. From that discussion it didn't seem like this addition would be too controversial or difficult. One thing I still don't understand is picking the values for the `stable` attribute. This is my first non-documentation PR for rust so I am open to any feedback on improvements.
Rollup of 11 pull requests Successful merges: - #58975 (Implement `iter::Sum` and `iter::Product` for `Option`) - #60542 (Add Step::sub_usize) - #60555 (Implement nth_back for RChunks(Exact)(Mut)) - #60766 (Weak::into_raw) - #61048 (Feature/nth back chunks) - #61191 (librustc_errors: Move annotation collection to own impl) - #61235 (Stabilize bufreader_buffer feature) - #61249 (Rename Place::local to Place::local_or_deref_local) - #61291 (Avoid unneeded bug!() call) - #61294 (Rename `TraitOrImpl` to `Assoc` and `trait_or_impl` to `assoc`.) - #61297 (Remove LLVM instruction stats and other (obsolete) codegen stats.) Failed merges: r? @ghost
I realize I'm late to this, but is there any reason for having the iter.map(|x| x.ok_or(())).sum::<Result<_, _>>().ok() |
This is similar to the existing implementation for
Result
. It will take each item into the accumulator unless aNone
is returned.I based a lot of this on #38580. From that discussion it didn't seem like this addition would be too controversial or difficult. One thing I still don't understand is picking the values for the
stable
attribute. This is my first non-documentation PR for rust so I am open to any feedback on improvements.